﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using EquipmentPanelTest.Data;

namespace EquipmentPanelTest.Controls
{
    /// <summary>
    /// Interaction logic for EquipmentGrid.xaml
    /// </summary>
    public partial class EquipmentGrid : UserControl
    {
        #region Dependency properties

        #region CellWidth

        public static readonly DependencyProperty CellWidthProperty =
            DependencyProperty.Register("CellWidth", typeof(double), typeof(EquipmentGrid), new PropertyMetadata(OnCellWidthPropertyChanged));

        public double CellWidth
        {
            get
            {
                return (double)GetValue(CellWidthProperty);
            }
            set
            {
                SetValue(CellWidthProperty, value);
            }
        }

        private static void OnCellWidthPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            EquipmentGrid control = d as EquipmentGrid;
            if (control != null)
            {
                control.DrawCells();
            }
        }

        #endregion

        #endregion

        public List<EquipmentItemControl> Items { get; private set; }

        public EquipmentGrid()
        {
            InitializeComponent();

            cells = new CoordinateMatrix<EquipmentGridCell>();
            Items = new List<EquipmentItemControl>();

            SizeChanged += new SizeChangedEventHandler(EquipmentGrid_SizeChanged);
            MouseMove += new MouseEventHandler(EquipmentGrid_MouseMove);
        }

        #region Public methods

        public void AddItem(ItemSize size, object content)
        {
            EquipmentGridCell firstCell = null;
            var freeCells = cells.ApplyFilter(c => !c.IsOccupied);
            foreach (var fc in freeCells)
            {
                if (cells.CheckFreeArea(fc, size.RowsCount, size.ColumnsCount))
                {
                    firstCell = fc;
                    break;
                }
            }

            if (firstCell != null)
            {
                double offset = GetItemOffset();

                var itemWidth = size.ColumnsCount * CellWidth - offset;
                var itemHeight = size.RowsCount * CellWidth - offset;

                EquipmentItemControl item = new EquipmentItemControl(size.RowsCount, size.ColumnsCount)
                {
                    Background = new SolidColorBrush(Colors.Gold),
                    Content = content,
                    Width = itemWidth,
                    Height = itemHeight,
                };
                item.PressedEvent += ItemPressedHandler;
                item.UnPressedEvent += ItemUnPressedHandler;

                LayoutRoot.Children.Add(item);
                Items.Add(item);

                UpdateLayoutForItem(item, firstCell);

                MeasureAvaliableCapacity();
            }
        }

        #endregion

        #region Private methods

        private void EquipmentGrid_MouseMove(object sender, MouseEventArgs e)
        {
            if (isHoldingItem && moveItemSettings != null)
            {
                var opos = moveItemSettings.HoldingPosition;
                var hitem = moveItemSettings.HoldingItem;

                var pos = e.GetPosition(LayoutRoot);

                var movingItemRect = GetRectForControl(hitem);

                Canvas.SetLeft(hitem, pos.X - opos.X);
                Canvas.SetTop(hitem, pos.Y - opos.Y);

                MeasureAvaliableCapacity();
            }
        }

        private void EquipmentGrid_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            DrawCells();
        }

        private void DrawCells()
        {
            ClearCells();

            var d1 = Width;

            double layoutWidth = LayoutRoot.ActualWidth;
            double layoutHeight = LayoutRoot.ActualHeight;

            int itemsCountHorizontal = (int)(layoutHeight / CellWidth);
            int itemsCountVertical = (int)(layoutWidth / CellWidth);

            cells.Setup(itemsCountHorizontal, itemsCountVertical);

            for (int i = 0; i < itemsCountHorizontal; ++i)
            {
                for (int j = 0; j < itemsCountVertical; ++j)
                {
                    EquipmentGridCell cell = new EquipmentGridCell(i, j)
                    {
                        Width = CellWidth,
                        Height = CellWidth,
                        Content = new TextBlock()
                        {
                            Text = String.Format("{0}x{1}", i, j)
                        }
                    };

                    LayoutRoot.Children.Add(cell);
                    cells[i][j] = cell;
                    Canvas.SetLeft(cell, j * CellWidth);
                    Canvas.SetTop(cell, i * CellWidth);
                }
            }
        }

        private void MeasureAvaliableCapacity()
        {
            cells.ForEach(c => c.IsOccupied = false);

            foreach (var control in Items)
            {
                Rect controlRect = GetRectForControl(control);

                foreach (var cell in cells.ToEnumerable())
                {
                    Rect cellRect = GetRectForControl(cell);

                    if (controlRect.IntersectsWith(cellRect))
                        cell.IsOccupied = true;
                }
            }

            HighlightIntersectingItems();
        }

        private void HighlightIntersectingItems()
        {
            //remove highligh from all items
            foreach (var i in Items)
            {
                i.SetIntersected(false);
            }

            var occupiedCells = cells.ApplyFilter(c => c.IsOccupied);

            List<Tuple<Rect, EquipmentItemControl>> itemRectangles = new List<Tuple<Rect, EquipmentItemControl>>();
            foreach (var item in Items)
            {
                itemRectangles.Add(new Tuple<Rect, EquipmentItemControl>(GetRectForControl(item), item));
            }

            HashSet<EquipmentItemControl> intersectingItems = new HashSet<EquipmentItemControl>();

            foreach (var cell in occupiedCells)
            {
                Rect cellRect = GetRectForControl(cell);

                var itemsInCell = GetItemsIntersectingWithCell(itemRectangles, cellRect);

                if (itemsInCell.Count() > 1)
                {
                    foreach (var i in itemsInCell)
                    {
                        intersectingItems.Add(i);
                    }
                }
            }

            foreach (var i in intersectingItems)
            {
                i.SetIntersected(true);
            }
        }

        private IEnumerable<EquipmentItemControl> GetItemsIntersectingWithCell(List<Tuple<Rect, EquipmentItemControl>> itemRectangles, Rect cellRectangle)
        {
            List<EquipmentItemControl> result = new List<EquipmentItemControl>();

            foreach (var tuple in itemRectangles)
            {
                if (tuple.Item1.IntersectsWith(cellRectangle))
                    result.Add(tuple.Item2);
            }

            return result;
        }

        private Rect GetRectForControl(UserControl control)
        {
            double top = Canvas.GetTop(control);
            double left = Canvas.GetLeft(control);


            if (double.IsNaN(top))
                top = 0;
            if (double.IsNaN(left))
                left = 0;

            return new Rect(left + 2, top + 2, control.Width - 2, control.Height - 2);
        }

        private void ClearCells()
        {
            cells.Clear();
            LayoutRoot.Children.Clear();
        }

        private void UpdateLayoutForItem(EquipmentItemControl item, EquipmentGridCell topLeftCell)
        {
            double offset = GetItemOffset();

            var rectangle = GetRectForControl(topLeftCell);
            Canvas.SetTop(item, rectangle.Top + offset / 4);
            Canvas.SetLeft(item, rectangle.Left + offset / 4);
        }

        private double GetItemOffset()
        {
            return CellWidth / 3;
        }

        private IEnumerable<EquipmentGridCell> GetCellsForItem(EquipmentItemControl item)
        {
            List<EquipmentGridCell> result = new List<EquipmentGridCell>();
            Rect itemRect = GetRectForControl(item);

            int rowsCount = cells.RowsCount;
            int columnsCount = cells.ColumnsCount;

            for (int i = 0; i < rowsCount; ++i)
            {
                for (int j = 0; j < columnsCount; ++j)
                {
                    var cell = cells[i][j];
                    Rect cellRect = GetRectForControl(cell);
                    if (itemRect.IntersectsWith(cellRect))
                    {
                        result.Add(cell);
                    }
                }
            }

            return result;
        }

        private EquipmentGridCell GetTopLeftCellForItem(EquipmentItemControl item)
        {
            var cells = GetCellsForItem(item);
            int minRow = cells.Min((c) => c.Row);
            int minColumn = cells.Min((c) => c.Column);

            var topLeftCell = cells.Where((c) => c.Row == minRow && c.Column == minColumn).FirstOrDefault();

            return topLeftCell;
        }

        private bool ItemIsOutsideGrid(EquipmentItemControl item)
        {
            var cells = GetCellsForItem(item);

            int layRowsCount = cells.GroupBy(c => c.Row).Count();
            int layColumnsCount = cells.GroupBy(c => c.Column).Count();

            return cells.Count() < item.RowsCount * item.ColumnsCount
                || (layRowsCount != item.RowsCount || layColumnsCount != item.ColumnsCount);
        }

        private void RevertToOriginalPosition(EquipmentItemControl holdingItem)
        {
            UpdateLayoutForItem(holdingItem, initialMovingCell);
        }

        #endregion

        #region Event handlers for items

        private void ItemPressedHandler(object sender, MouseButtonEventArgs e)
        {
            var holdingItem = sender as EquipmentItemControl;
            if (holdingItem != null)
            {
                holdingItem.SetSelected(true);

                var holdingPosition = e.GetPosition(holdingItem);
                moveItemSettings = new Data.MoveSettings(holdingItem, holdingPosition);
                initialMovingCell = GetTopLeftCellForItem(holdingItem);
                isHoldingItem = true;
            }
        }

        private void ItemUnPressedHandler(object sender, MouseButtonEventArgs e)
        {
            var holdingItem = sender as EquipmentItemControl;
            if (holdingItem != null)
            {
                holdingItem.SetSelected(false);

                if (ItemIsOutsideGrid(holdingItem)) // we've moved item outside grid
                {
                    RevertToOriginalPosition(holdingItem);
                }
                else
                {
                    if (holdingItem.IsIntersected) // we've moved item so it intersects with another one(s)
                    {
                        RevertToOriginalPosition(holdingItem);
                    }
                    else // drop item and align it inside cells
                    {
                        var itemCell = GetTopLeftCellForItem(holdingItem);
                        if (itemCell != null)
                        {
                            UpdateLayoutForItem(holdingItem, itemCell);
                        }
                    }
                }

                MeasureAvaliableCapacity();
            }

            moveItemSettings = null;
            isHoldingItem = false;
        }

        #endregion

        #region Private fields

        private bool isHoldingItem;
        private EquipmentGridCell initialMovingCell;
        private MoveSettings moveItemSettings;
        private CoordinateMatrix<EquipmentGridCell> cells;

        #endregion
    }
}
